#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <mutex>
#include <algorithm>
#include "../comm/httplib.h"
#include <jsoncpp/json/json.h>
// #include "oj_model_file.hpp" // 使用文件版的题库
#include "./oj_model_database.hpp" // 使用数据库版的题库
#include "oj_view.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"

namespace ns_controller
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_view;
    using namespace httplib;
    using namespace ns_model;

    // 配置文件所在的路径
    const string service_machine = "./conf/service_machine.conf";

    // 主机
    struct Machine
    {
        Machine(): _ip(""), _port(0), _load(0)
        {}       

        ~Machine() {}       

        // 提升主机负载
        void IncLoad()
        {
            if(_mtx) _mtx->lock();
            ++_load;
            if(_mtx) _mtx->unlock();
        } 

        // 减少主机负载
        void DescLoad()
        {
            if(_mtx) _mtx->lock();
            --_load;
            if(_mtx) _mtx->unlock();
        }

        // 重置主机负载
        void ResetLoad()
        {
            if(_mtx) _mtx->lock();
            _load = 0;
            if(_mtx) _mtx->unlock();
        }

        // 获取主机负载(该接口仅仅是为了统一接口)
        uint64_t getLoad()
        {
            uint64_t load = 0;
            if(_mtx) _mtx->lock();
            load = _load;
            if(_mtx) _mtx->unlock();
            return load;
        }

        std::string _ip;   // 编译服务的 IP 地址
        int _port;         // 编译服务的端口号
        uint64_t _load;    // 编译服务的负载
        mutex* _mtx;       // 保护 _load 的互斥锁
    };

    // 负载均衡模块
    class LoadBalance
    {
    public:
        LoadBalance()
        {
            assert(LoadConf(service_machine));
        }

        ~LoadBalance() {}

        // 加载主机配置文件
        bool LoadConf(const string& machine_conf)
        {
            ifstream in(machine_conf);
            if(!in.is_open())
            {
                LOG(FATAL) << "加载配置文件失败！\n";
                return false;
            }

            string line;
            while(getline(in, line))
            {
                vector<string> tokens;
                StringUtil::SplitString(line, &tokens, ":");
                if(tokens.size() != 2)
                {
                    LOG(WARNING) << "切分 " << line << " 失败！\n";
                    continue;
                }

                Machine m;
                m._ip = tokens[0];
                m._port = stoi(tokens[1]);
                m._mtx = new mutex();

                _online.push_back(_machines.size());
                _machines.push_back(m);
            }

            LOG(INFO) << "加载配置文件成功\n";
            return true; 
        }

        // 选择负载最低的在线主机
        // id 和 m 都是输出型参数
        bool SmartChoice(int* id, Machine** m)
        {
            _mtx.lock();
            int online_num = _online.size();
            if(online_num == 0)
            {
                _mtx.unlock();
                LOG(FATAL) << "所有主机已经离线, 无法提供编译运行服务\n";
                return false;
            }

            // 遍历找出负载最小的主机
            uint64_t min_load = _machines[_online[0]].getLoad();
            *id = _online[0];
            *m = &_machines[_online[0]];
            for(int i = 1; i < online_num; ++i)
            {
                int load = _machines[_online[i]].getLoad();
                if(min_load > load)
                {
                    min_load = load;
                    *id = _online[i];
                    *m = &_machines[_online[i]];
                }
            }

            _mtx.unlock();
            return true;
        }

        // 将 id 主机离线
        void OfflineMachine(int id)
        {
            _mtx.lock();
            for(auto it = _online.begin(); it != _online.end(); ++it)
            {
                if(*it == id)
                {
                    _machines[id].ResetLoad();
                    _online.erase(it);
                    _offline.push_back(id);
                    break; // 因为 break 的存在, 所以不需要考虑迭代器问题
                }
            }
            _mtx.unlock();
        }

        // 将主机全部上线
        void OnlineMachine()
        {
            _mtx.lock();
            _online.insert(_online.end(), _offline.begin(), _offline.end());
            _offline.clear();
            _mtx.unlock();

            LOG(INFO) << "所有主机上线成功！\n";
        }

        // 用于测试的接口
        void ShowMachines()
        {
            _mtx.lock();
            cout << "当前在线的主机: ";
            for(auto &id : _online) cout << id << " ";
            cout << endl;
            
            cout << "当前离线的主机: ";
            for(auto &id : _offline) cout << id << " ";
            cout << endl;
            _mtx.unlock();
        }

    private:
        // 可以提供编译服务的主机
        // 每台主机都有自己的 id, 使用下标来充当 id
        vector<Machine> _machines; 
        // 所有在线主机的 id
        vector<int> _online;
        // 所有离线主机的 id
        vector<int> _offline;
        // 保证负载均衡模块的安全
        mutex _mtx;
    };

    class Controller
    {
    public:
        Controller() {}
        ~Controller() {}
        
        // 根据题目数据构建网页
        // html: 输出型参数
        // 获取题目列表
        bool AllQuestions(string* html)
        {
            bool ret = true;
            vector<Question> all;
            if(_model.GetAllQuestions(&all))
            {
                // 解决题号乱序问题
                sort(all.begin(), all.end(), [](const Question& q1, const Question& q2){
                    return stoi(q1.number) < stoi(q2.number);
                });
                // 获取全部题目信息成功, 将所有的题目信息构建成 html 网页
                _view.AllExpandHtml(all, html);
            }
            else
            {
                *html = "获取题目失败, 形成题目列表失败！";
                ret = false;
            }
            return ret;
        }

        // 获取具体的某一道题目
        bool OneQuestion(const string& number, string* html)
        {
            bool ret = true;
            Question q;
            if(_model.GetOneQuestion(number, &q))
            {
                // 获取指定题目信息成功, 将该题的信息构建成 html 网页
                _view.OneExpandHtml(q, html);
            }
            else 
            {
                *html = "指定题目 " + number + " 获取失败！";
                ret = false;
            }
            return ret;
        }

        // in_json 包括 code 和 input
        void Judge(const string& number, const string& in_json, string* out_json)
        {
            // 1. 根据题目编号获取题目信息
            Question q;
            _model.GetOneQuestion(number, &q);

            // 2. in_json 进行反序列化, 获取到用户提交的代码
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            string code = in_value["code"].asString(); // 用户提交的代码

            // 3. 将用户提交的代码和测试用例代码进行拼接
            Json::Value compile_value;
            compile_value["input"] = in_value["input"].asString();
            compile_value["code"] = code + "\n" + q.tail;
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;
            
            // 4. 将 compile_value 进行序列化, 并将 JSON 格式
            // 的字符串通过网络请求提交给编译运行服务
            Json::FastWriter writer;
            string compile_json = writer.write(compile_value);

            // 5. 选择负载最低的主机来编译运行代码
            // 一直选择直到有主机可用, 否则主机都挂掉了
            while(true)
            {
                int id = 0;
                Machine* m = nullptr;
                if(!_load_balance.SmartChoice(&id, &m))
                {
                    // 主机全部挂掉了, 直接 break 退出循环
                    break;
                }

                // 6. 向编译运行服务发起 http 请求, 拿到结果
                Client client(m->_ip, m->_port); // Client 是 cpp-http 库实现的客户端
                m->IncLoad();
                LOG(INFO) << "主机信息：" << id << " " << m->_ip << " " << m->_port << " 负载: " << m->_load << endl;
                // 以 Post 方式请求编译运行服务
                if(auto ret = client.Post("/compile_and_run", compile_json, "application/json; charset=utf-8"))
                {
                    // 7. 将结果赋值给 out_json
                    if(ret->status == 200) // 此次请求成功
                    {
                        *out_json = ret->body;
                        LOG(INFO) << "请求编译运行服务成功\n";
                        m->DescLoad(); 
                        break;
                    }
                    else
                    {
                        // 状态码不是 200 的情况
                        LOG(WARNING) << "此次请求的状态码为 " << ret->status << endl;
                        m->DescLoad();
                    }
                }
                else
                {
                    // 请求失败
                    LOG(ERROR) << "主机信息：" << id << " " << m->_ip << " " << m->_port << " 可能已经离线\n";
                    _load_balance.OfflineMachine(id);
                    _load_balance.ShowMachines(); // 测试使用
                }
            }
        }

        void RecoveryMachine()
        {
            _load_balance.OnlineMachine();
        }

    private:
        Model _model; // 提供后台数据
        View _view; // 提供网页渲染功能
        LoadBalance _load_balance;
    };
}